#include "board.hpp"
#include "chess_piece.hpp"
#include <vector>
#include <string>
#include <memory>
#include <algorithm>

Camp operator!(const Camp &c) {
    return c == Camp::RED ? Camp::BLACK : Camp::RED;
}

std::shared_ptr<ChessPiece> ChessPiece::parse(std::string str, Camp camp) {
    if(str == "King") return std::make_shared<King>(camp);
    if(str == "Rock") return std::make_shared<Rock>(camp);
    if(str == "Cannon") return std::make_shared<Cannon>(camp);
    if(str == "Knight") return std::make_shared<Knight>(camp);
    if(str == "Guard") return std::make_shared<Guard>(camp);
    if(str == "Bishop") return std::make_shared<Bishop>(camp);
    if(str == "Pawn") return std::make_shared<Pawn>(camp);
    throw "Parse failed!";
}

bool ChessPiece::is_available(const Board &board, const Move &move) const {
    auto available = available_move(board, move.from());
    auto it = std::find_if(available.begin(), available.end(), 
        [move](const Move &m) -> bool {
            return m == move;
        });
    return it != available.end();
}

std::shared_ptr<ChessPiece> King::clone() const {
    return std::make_shared<King>(*this);
}

std::shared_ptr<ChessPiece> Rock::clone() const {
    return std::make_shared<Rock>(*this);
}

std::shared_ptr<ChessPiece> Cannon::clone() const {
    return std::make_shared<Cannon>(*this);
}

std::shared_ptr<ChessPiece> Knight::clone() const {
    return std::make_shared<Knight>(*this);
}

std::shared_ptr<ChessPiece> Guard::clone() const {
    return std::make_shared<Guard>(*this);
}

std::shared_ptr<ChessPiece> Bishop::clone() const {
    return std::make_shared<Bishop>(*this);
}

std::shared_ptr<ChessPiece> Pawn::clone() const {
    return std::make_shared<Pawn>(*this);
}

bool King::is_valid(const int x, const int y) const {
    return x >= 3 && x <= 5 && y >= 0 && y<= 2;
}

std::vector<Move> King::available_move(const Board &board, const Location loc) const {
    std::vector<Move> res;
    int direction[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
    for(int i = 0 ; i < 4 ; i++) {
        int x = loc.x + direction[i][0];
        int y = loc.y + direction[i][1];
        if(is_valid(x, y)) {
            Location nxt = Location(x, y);
            if(!board.at(nxt) || board.at(nxt)->camp() != camp())
                res.push_back(Move(loc, nxt));
        }
    }
    int y = loc.y;
    while(Location::is_valid(loc.x, ++y)) {
        if(board.at(Location(loc.x, y))) {
            if(board.at(Location(loc.x, y))->type() == ChessPiece::Type::KING)
                res.push_back(Move(loc, Location(loc.x, y)));
            break;
        }
    }
    return res;
}

std::vector<Move> Rock::available_move(const Board &board, const Location loc) const {
    std::vector<Move> res;
    int direction[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
    for(int i = 0 ; i < 4 ; i++) {
        int x = loc.x;
        int y = loc.y;
        while(true) {
            x += direction[i][0];
            y += direction[i][1];
            if(!Location::is_valid(x, y)) break;
            Location nxt = Location(x, y);
            if(!board.at(nxt)) {
                res.push_back(Move(loc, nxt));
            } else {
                if(board.at(nxt)->camp() != camp()) res.push_back(Move(loc, nxt));
                break;
            }
        }
    }
    return res;
}

std::vector<Move> Cannon::available_move(const Board &board, const Location loc) const {
    std::vector<Move> res;
    int direction[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
    for(int i = 0 ; i < 4 ; i++) {
        int x = loc.x;
        int y = loc.y;
        bool jump = false;
        while(true) {
            x += direction[i][0];
            y += direction[i][1];
            if(!Location::is_valid(x, y)) break;
            Location nxt(x, y);
            if(jump) {
                if(board.at(nxt)) {
                    if(board.at(nxt)->camp() != camp()) {
                        res.push_back(Move(loc, nxt));
                    }
                    break;
                }
            } else {
                if(board.at(nxt)) {
                    jump = true;
                } else {
                    res.push_back(Move(loc, nxt));
                }
            }
        }
    }
    return res;
}

std::vector<Move> Knight::available_move(const Board &board, const Location loc) const {
    std::vector<Move> res;
    int path[8][2][2] = {{{-1, 0}, {-2, -1}}, {{-1, 0}, {-2, 1}},
        {{1, 0}, {2, -1}}, {{1, 0}, {2, 1}},
        {{0, -1}, {-1, -2}}, {{0, -1}, {1, -2}},
        {{0, 1}, {-1, 2}}, {{0, 1}, {1, 2}}};
    for(int i = 0 ; i < 8 ; i++) {
        int trip_x = loc.x + path[i][0][0];
        int trip_y = loc.y + path[i][0][1];
        if(Location::is_valid(trip_x, trip_y) && !board.at(Location(trip_x, trip_y))) {
            int dest_x = loc.x + path[i][1][0];
            int dest_y = loc.y + path[i][1][1];
            if(Location::is_valid(dest_x, dest_y)) {
                Location nxt(dest_x, dest_y);
                if(!board.at(nxt) || board.at(nxt)->camp() != camp())
                    res.push_back(Move(loc, nxt));
            }
        }
    }
    return res;
}

bool Guard::is_valid(const int x, const int y) const {
    return x >= 3 && x <= 5 && y >= 0 && y<= 2;
}

std::vector<Move> Guard::available_move(const Board &board, const Location loc) const {
    std::vector<Move> res;
    int direction[4][2] = {{-1, 1}, {-1, -1}, {1, -1}, {1, 1}};
    for(int i = 0 ; i < 4 ; i++) {
        int x = loc.x + direction[i][0];
        int y = loc.y + direction[i][1];
        if(is_valid(x, y)) {
            Location nxt = Location(x, y);
            if(!board.at(nxt) || board.at(nxt)->camp() != camp())
                res.push_back(Move(loc, nxt));
        }
    }
    return res;
}

bool Bishop::is_valid(const int x, const int y) const {
    return x >= 0 && x < 9 && y >= 0 && y <= 4;
}

std::vector<Move> Bishop::available_move(const Board &board, const Location loc) const {
    std::vector<Move> res;
    int path[4][2][2] = {{{-1, -1}, {-2, -2}}, {{-1, 1}, {-2, 2}},
        {{1, -1}, {2, -2}}, {{1, 1}, {2, 2}}};
    for(int i = 0 ; i < 4 ; i++) {
        int trip_x = loc.x + path[i][0][0];
        int trip_y = loc.y + path[i][0][1];
        if(is_valid(trip_x, trip_y) && !board.at(Location(trip_x, trip_y))) {
            int dest_x = loc.x + path[i][1][0];
            int dest_y = loc.y + path[i][1][1];
            if(is_valid(dest_x, dest_y)) {
                Location nxt(dest_x, dest_y);
                if(!board.at(nxt) || board.at(nxt)->camp() != camp())
                    res.push_back(Move(loc, nxt));
            }
        }
    }
    return res;
}

std::vector<Move> Pawn::available_move(const Board &board, const Location loc) const {
    std::vector<Move> res;
    int direction[3][2] = {{-1, 0}, {1, 0}, {0, 1}};
    if(loc.y <= 4) {
        Location nxt(loc.x, loc.y + 1);
        if(!board.at(nxt) || board.at(nxt)->camp() != camp())
            res.push_back(Move(loc, nxt));
    } else {
        for(int i = 0 ; i < 3 ; i++) {
            int x = loc.x + direction[i][0];
            int y = loc.y + direction[i][1];
            if(Location::is_valid(x, y)) {
                Location nxt(x, y);
                if(!board.at(nxt) || board.at(nxt)->camp() != camp())
                    res.push_back(Move(loc, nxt));
            }
        }
    }
    return res;
}